package ibmcloud

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/openshift/cloud-credential-operator/pkg/cmd/provisioning"
	"github.com/openshift/cloud-credential-operator/pkg/ibmcloud"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

// APIKeyEnvVars is a list of environment variable names containing an IBM Cloud API key
var APIKeyEnvVars = []string{"IC_API_KEY", "IBMCLOUD_API_KEY", "BM_API_KEY", "BLUEMIX_API_KEY"}

var (
	// Options captures the options that affect creation/deletion of the generated
	// objects.
	Options = options{
		TargetDir: "",
	}

	// APIKeyName is the name for the autogenerated API Key for the Service ID
	APIKeyName = "ccoctl-generated-key"
)

// getEnv reads the content from first found environment variable from the envs list, returns empty string if none found.
func getEnv(envs []string) string {
	for _, k := range envs {
		if v := os.Getenv(k); v != "" {
			return v
		}
	}
	return ""
}

// NewCreateServiceIDCmd provides the "create-service-id" subcommand
func NewCreateServiceIDCmd() *cobra.Command {
	createServiceIDCmd := &cobra.Command{
		Use:              "create-service-id",
		Short:            "Create Service ID",
		RunE:             createServiceIDCmd,
		PersistentPreRun: initEnvForCreateServiceIDCmd,
	}

	createServiceIDCmd.PersistentFlags().StringVar(&Options.Name, "name", "", "User-defined name for all created IBM Cloud resources (can be separate from the cluster's infra-id)")
	createServiceIDCmd.MarkPersistentFlagRequired("name")
	createServiceIDCmd.PersistentFlags().StringVar(&Options.CredRequestDir, "credentials-requests-dir", "", "Directory containing files of CredentialsRequests to create IAM Roles for (can be created by running 'oc adm release extract --credentials-requests --cloud=ibmcloud' against an OpenShift release image)")
	createServiceIDCmd.MarkPersistentFlagRequired("credentials-requests-dir")
	createServiceIDCmd.PersistentFlags().StringVar(&Options.ResourceGroupName, "resource-group-name", "", "Name of the resource group used for scoping the access policies")
	createServiceIDCmd.PersistentFlags().StringVar(&Options.TargetDir, "output-dir", "", "Directory to place generated files (defaults to current directory)")
	createServiceIDCmd.PersistentFlags().BoolVar(&Options.EnableTechPreview, "enable-tech-preview", false, "Opt into processing CredentialsRequests marked as tech-preview")

	return createServiceIDCmd
}

func createServiceIDCmd(cmd *cobra.Command, args []string) error {
	apiKey := getEnv(APIKeyEnvVars)
	if apiKey == "" {
		return fmt.Errorf("%s environment variable not set", APIKeyEnvVars)
	}

	params := &ibmcloud.ClientParams{
		InfraName: Options.Name,
	}

	ibmclient, err := ibmcloud.NewClient(apiKey, params)
	if err != nil {
		return err
	}

	apiKeyDetailsOptions := ibmclient.NewGetAPIKeysDetailsOptions()
	apiKeyDetailsOptions.SetIamAPIKey(apiKey)
	apiKeyDetails, _, err := ibmclient.GetAPIKeysDetails(apiKeyDetailsOptions)
	if err != nil {
		return errors.Wrap(err, "Failed to get Details for the given APIKey")
	}

	err = createServiceIDs(ibmclient, apiKeyDetails.AccountID, Options.Name, Options.ResourceGroupName,
		Options.CredRequestDir, Options.TargetDir, Options.EnableTechPreview)
	if err != nil {
		return err
	}

	return nil
}

func createServiceIDs(client ibmcloud.Client, accountID *string,
	name, resourceGroupName, credReqDir, targetDir string, enableTechPreview bool) error {

	resourceGroupID, err := getResourceGroupID(client, accountID, resourceGroupName)
	if err != nil {
		return errors.Wrap(err, "Failed to getResourceGroupID")
	}

	// Process directory
	credReqs, err := provisioning.GetListOfCredentialsRequests(credReqDir, enableTechPreview)
	if err != nil {
		return errors.Wrap(err, "Failed to process files containing CredentialsRequests")
	}

	var serviceIDs []*ServiceID

	undo := func() {
		for _, serviceID := range serviceIDs {
			serviceID.UnDo(targetDir)
		}
	}

	for _, cr := range credReqs {
		serviceID := NewServiceID(client, name, *accountID, resourceGroupID, cr)
		serviceIDs = append(serviceIDs, serviceID)
	}

	for _, serviceID := range serviceIDs {
		if err := serviceID.Validate(); err != nil {
			return errors.Wrap(err, "Failed to validate the serviceID")
		}
	}

	for _, serviceID := range serviceIDs {
		if err := serviceID.Do(); err != nil {
			undo()
			return errors.Wrap(err, "Failed to process the serviceID")
		}
	}

	for _, serviceID := range serviceIDs {
		if err := serviceID.Dump(targetDir); err != nil {
			undo()
			return errors.Wrap(err, "Failed to generate the secrets for serviceIDs")
		}
	}

	return nil
}

// initEnvForCreateServiceIDCmd will ensure the destination directory is ready to receive the generated
// files, and will create the directory if necessary.
func initEnvForCreateServiceIDCmd(cmd *cobra.Command, args []string) {
	if Options.TargetDir == "" {
		pwd, err := os.Getwd()
		if err != nil {
			log.Fatalf("Failed to get current directory: %s", err)
		}

		Options.TargetDir = pwd
	}

	fPath, err := filepath.Abs(Options.TargetDir)
	if err != nil {
		log.Fatalf("Failed to resolve full path: %s", err)
	}

	// create target dir if necessary
	err = provisioning.EnsureDir(fPath)
	if err != nil {
		log.Fatalf("failed to create target directory at %s", fPath)
	}

	// create manifests dir if necessary
	manifestsDir := filepath.Join(fPath, manifestsDirName)
	err = provisioning.EnsureDir(manifestsDir)
	if err != nil {
		log.Fatalf("failed to create manifests directory at %s", manifestsDir)
	}
}
